package com.xlf.service.base.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.UUID;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageInfo;
import com.xlf.common.auth.util.SecurityUtils;
import com.xlf.common.core.exception.BizException;
import com.xlf.common.core.util.StringUtils;
import com.xlf.common.core.util.file.UploadConfig;
import com.xlf.common.core.util.page.PageUtils;
import com.xlf.common.core.util.page.TableDataInfo;
import com.xlf.pojo.base.*;
import com.xlf.pojo.constant.Constants;
import com.xlf.service.base.manager.ProblemCommonService;
import com.xlf.service.base.mapper.*;
import com.xlf.service.base.pojo.dto.ProblemReqDTO;
import com.xlf.service.base.pojo.vo.*;
import com.xlf.service.base.service.ProblemService;
import com.xlf.service.base.service.impl.validate.ProblemValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

import static com.xlf.common.core.util.page.PageUtils.startPage;

/**
* @author 小新
* @description 针对表【problem】的数据库操作Service实现
* @createDate 2023-10-03 10:21:22
*/
@Service
public class ProblemServiceImpl extends ServiceImpl<ProblemMapper, Problem>
    implements ProblemService{


    @Resource
    ProblemMapper problemMapper;

    @Resource
    DifficultyMapper difficultyMapper;

    @Resource
    JudgeMapper judgeMapper;

    @Resource
    TagMapper tagMapper;


    @Resource
    ProblemTagMapper problemTagMapper;

    @Resource
    ProblemCaseMapper problemCaseMapper;

    @Resource
    ProblemLanguageMapper problemLanguageMapper;

    @Resource
    LanguageMapper languageMapper;

    @Resource
    ProblemTypeMapper problemTypeMapper;


    @Resource
    ProblemProblemListMapper problemProblemListMapper;

    @Resource
    ContestProblemMapper contestProblemMapper;

    @Autowired
    ProblemValidator problemValidator;

    @Resource
    ProblemCommonService problemCommonService;

    @Override
    public TableDataInfo<ProblemVO> getProblemVOList(ProblemReqDTO reqDTO) {

        QueryWrapper<ProblemVO> wrapper = new QueryWrapper<>();

        wrapper.eq(reqDTO.getProblemTypeId() != null,"t1.problem_type_id",reqDTO.getProblemTypeId())
        .eq(reqDTO.getDifficultyId() !=null,"t1.difficulty_id ",reqDTO.getDifficultyId())
                .eq("t1.status",com.xlf.common.core.constant.Constants.NORMAL)
                .eq("t1.auth",Constants.ProblemAuth.PUBLIC.getCode())
                .in(StringUtils.isNotEmpty(reqDTO.getTagIds()),"t4.tag_id",reqDTO.getTagIds())
                .likeRight(StringUtils.isNotEmpty(reqDTO.getProblemId()),"t1.problem_id",reqDTO.getProblemId())
                .like(StringUtils.isNotEmpty(reqDTO.getSearchKey()),"t1.title",reqDTO.getSearchKey())
                .orderByAsc("t1.problem_id")
                .groupBy("t1.id"); //需要分组，分页的最小单位是一组

        startPage();
        List<ProblemVO> problemList = problemMapper.getProblemVOList(wrapper);

        if(StringUtils.isEmpty(problemList))
            return PageUtils.getEmptyDataTable();

        problemList.forEach(item -> {
            item.setJudgeStatusVOS(problemCommonService.judgeStatusVOList(item.getId()));
        });
         return PageUtils.getDataTable(problemList,new PageInfo(problemList).getTotal());
    }



    @Override
    public List<Problem> getProblemList(Problem problem) {

        QueryWrapper<Problem> wrapper = new QueryWrapper<>();

        wrapper.eq(problem.getProblemTypeId() != null,"t1.problem_type_id",problem.getProblemTypeId())
                .eq(problem.getDifficultyId() != null,"t1.difficulty_id ",problem.getDifficultyId())
                .likeRight(problem.getProblemId() != null,"t1.problem_id ",problem.getProblemId())
                .eq(problem.getAuthorId() != null,"t1.author_id",problem.getAuthorId())
                .eq(problem.getAuth() != null,"t1.auth",problem.getAuth())
                .eq(problem.getStatus() != null,"t1.status",problem.getStatus())
                .in(StringUtils.isNotEmpty(problem.getTagIds()),"t4.tag_id",problem.getTagIds())
                .like(StringUtils.isNotEmpty(problem.getTitle()),"t1.title",problem.getTitle())
                .between(StringUtils.isNotEmpty(problem.getParams()) &&
                        problem.getParams().get("beginTime") != null && problem.getParams().get("endTime") != null,
                        "t1.create_time", problem.getParams().get("beginTime"),
                        problem.getParams().get("endTime"))
                .orderByAsc("t1.problem_id")
                .groupBy("t1.id"); //需要分组，分页的最小单位是一组

        List<Problem> problemList = problemMapper.getProblemList(wrapper);
        if(StringUtils.isEmpty(problemList))
            return new ArrayList<>();

        return problemList;
    }

    @Override
    public Problem getProblem(Long id) {

        Problem problem = getById(id);
        Assert.notNull(problem,() -> new BizException("题目不存在！"));

        //获取题库
        problem.setProblemType(problemTypeMapper.selectById(problem.getProblemTypeId()));

        //获取难度
        problem.setDifficulty(difficultyMapper.selectById(problem.getDifficultyId()));

        //获取语言
        List<ProblemLanguage> languageList = problemLanguageMapper.selectList(new LambdaQueryWrapper<ProblemLanguage>()
                .eq(ProblemLanguage::getPid, id));

        if(StringUtils.isNotEmpty(languageList)){
            Set<Long> collect = languageList.stream().map(item -> item.getLid()).collect(Collectors.toSet());
            problem.setProblemLanguageList(languageList);
            List<Language> languages = languageMapper.selectBatchIds(collect);
            if(StringUtils.isNotEmpty(languages)){
                Map<Long, String> stringMap = languages.stream().collect(Collectors.toMap(t -> t.getId(), t -> t.getName()));
                languageList.forEach(item -> {
                    item.setName(stringMap.get(item.getLid()));
                });
            }
            problem.setLanguageList(languages);
        }

        //获取标签
        List<Tag> problemTags = problemCommonService.getProblemTags(id, (lambdaQueryWrapper) -> {});
        problem.setTagList(problemTags);
        List<Long> collect = problemTags.stream().map(item -> item.getId()).collect(Collectors.toList());
        problem.setTagIds(collect);

        //获取测试用例
        List<ProblemCase> problemCases = problemCaseMapper.selectList(new LambdaQueryWrapper<ProblemCase>()
                .eq(ProblemCase::getPid, id));
        problem.setProblemCase(problemCases);

        return problem;
    }

    @Transactional
    @Override
    public Problem saveOrUpdate_(Problem problem) {

        //创建人
        if(problem.getAuthorId() == null){
            Object userId = SecurityUtils.getUserId();
            problem.setAuthorId((Long) userId);
        }


        // 如果没有提供problemId,则或者生成 P1000之类的，以problem表的id作为数字
        if (StringUtils.isEmpty(problem.getProblemId())) {
            problem.setProblemId(UUID.fastUUID().toString());
            Assert.isTrue(saveOrUpdate(problem),() -> new BizException("新增题目错误"));
            String problemIdPrefix = Optional.ofNullable(problem.getProblemIdPrefix()).orElse("P");
            lambdaUpdate().set(Problem::getProblemId, problemIdPrefix + problem.getId())
                    .eq(Problem::getId, problem.getId())
                    .update();
            problem.setProblemId("P" + problem.getId());
        } else {
            // problem_id唯一性检查
            String problemId = problem.getProblemId().toUpperCase();
            String problemIdPrefix = problem.getProblemIdPrefix();
            if(problemIdPrefix != null)
                problemId = problemIdPrefix + problemId;

            LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<Problem>()
                    .eq(Problem::getProblemId, problemId);
            if(problem.getId() != null){
                queryWrapper.ne(Problem::getId,problem.getId());
            }
            Long existedProblem = count(queryWrapper);
            if (existedProblem > 0) {
                throw new BizException("problem_id [" + problemId + "] 已经存在了！");
            }
            problem.setProblemId(problemId);
            Assert.isTrue(saveOrUpdate(problem),() -> new BizException("新增题目错误"));
        }

        //新增标签
        problemTagMapper.delete(new LambdaQueryWrapper<ProblemTag>()
                .eq(ProblemTag::getProblemId,problem.getId()));
        List<Long> tagIds = problem.getTagIds();
        if(StringUtils.isNotEmpty(tagIds)){
            tagIds.forEach(tagId ->{
                ProblemTag problemTag = new ProblemTag();
                problemTag.setProblemId(problem.getId());
                problemTag.setTagId(tagId);
                problemTagMapper.insert(problemTag);
            });
        }

        //新增测试用例
        problemCaseMapper.delete(new LambdaQueryWrapper<ProblemCase>()
                .eq(ProblemCase::getPid,problem.getId()));
        List<ProblemCase> problemCase = problem.getProblemCase();


        if(StringUtils.isNotEmpty(problemCase)){
            for (int i = 0; i < problemCase.size(); i++) {
                ProblemCase item = problemCase.get(i);
                item.setPid(problem.getId());
                item.setSeq(i + 1);
                problemCaseMapper.insert(item);
            }
            //更新题目总分
            Integer ioScoce = problemCase.stream().map(ProblemCase::getScore).reduce(0, Integer::sum);
            lambdaUpdate().set(Problem::getIoScore,ioScoce).eq(Problem::getId,problem.getId()).update();
        }

        //新增语言
        problemLanguageMapper.delete(new LambdaQueryWrapper<ProblemLanguage>()
                .eq(ProblemLanguage::getPid,problem.getId()));
        List<ProblemLanguage> languageList = problem.getProblemLanguageList();
        if(StringUtils.isNotEmpty(languageList)){
            languageList.forEach(item -> {
                item.setPid(problem.getId());
                problemLanguageMapper.insert(item);
            });
        }

        return problem;
    }


    @Override
    public boolean updateStatus(Problem problem) {
        return lambdaUpdate().set(Problem::getStatus,problem.getStatus()).eq(Problem::getId,problem.getId()).update();
    }

    @Transactional
    @Override
    public void copyProblem(List<Long> ids) {

        Assert.notEmpty(ids,() -> new BizException("列表为空！"));
        ids.forEach(item -> {
            Problem problem = getProblem(item);
            problem.setId(null);
            problem.setProblemId(problem.getProblemId() + "_copy");
            saveOrUpdate_(problem);
        });

    }

    @Transactional
    @Override
    public void correctionProblem(List<Problem> problemList) {

        Assert.notEmpty(problemList,() -> new BizException("列表为空！"));

        updateBatchById(problemList);

    }

    @Transactional
    @Override
    public void delete(List<Long> ids) {

        ids.forEach(item -> {
            if(contestProblemMapper.exists(new LambdaQueryWrapper<ContestProblem>().eq(ContestProblem::getPid,item))){
                throw new BizException("id为:" + item + "题目被比赛引用！");
            }
            if(problemProblemListMapper.exists(new LambdaQueryWrapper<ProblemProblemList>().eq(ProblemProblemList::getProblemId,item))){
                throw new BizException("id为:" + item + "题目被题单引用！");
            }
        });
        removeByIds(ids);
    }


    @Override
    public List<ProblemVO> getRecentlyProblemVOList() {

        List<Problem> list = lambdaQuery()
                .select(Problem::getId,Problem::getProblemId,Problem::getTitle,Problem::getDifficultyId)
                .eq(Problem::getStatus, com.xlf.common.core.constant.Constants.NORMAL)
                .eq(Problem::getAuth,Constants.ProblemAuth.PUBLIC)
                .orderByDesc(Problem::getId)
                .last("limit 5")
                .list();

        List<ProblemVO> problemVOS = BeanUtil.copyToList(list, ProblemVO.class);

        problemVOS.forEach(item -> {
            item.setTagList(problemCommonService.getProblemTags(item.getId()));
            item.setDifficulty(difficultyMapper.selectById(item.getDifficultyId()));
            item.setJudgeStatusVOS(problemCommonService.judgeStatusVOList(item.getId()));
        });
        return problemVOS;
    }


    @Override
    public void exportProblem(List<Long> ids) {

        //导出文件夹
        long millis = System.currentTimeMillis();
        String fileDir = UploadConfig.getLocalPath() +
                Constants.File.EXPORT_FOLDER.getPath() +
                File.separator + String.valueOf(millis);

        FileUtil.mkdir(fileDir);

        ids.forEach(item -> {

            Problem problem = getProblem(item);

            //该测试用例文件导出路径
            String testCasePath = fileDir + File.separator + item.toString();
            FileUtil.mkdir(testCasePath);
            //导出测试用例
            exportTestCase(problem, testCasePath);

            //导出题目基本消息
            ImportProblemVO importProblemVO = new ImportProblemVO();
            //语言模板
            importProblemVO.setCodeTemplates(getCodeTemplates(problem));
            //语言名称
            importProblemVO.setLanguages(getLangNames(problem));
            //标签名称
            importProblemVO.setTags(getTags(problem));

            problem.setId(null);
            problem.setDifficulty(null);
            problem.setProblemType(null);
            problem.setTagList(null);
            problem.setTagIds(null);
            problem.setProblemLanguageList(null);
            problem.setLanguageList(null);
            problem.setCreateTime(null);
            problem.setCreateBy(null);
            problem.setUpdateBy(null);
            problem.setUpdateTime(null);
            problem.setProblemCase(null);
            importProblemVO.setProblem(BeanUtil.beanToMap(problem,false,true));
            String problemPath = fileDir + File.separator + item + ".json";
            FileUtil.writeUtf8String(JSONUtil.toJsonStr(importProblemVO),problemPath);

        });
    }

    /**
     *
     * @param problem 导出的题目
     * @param testCasePath 测试用例导出的路径
     */
    private void exportTestCase(Problem problem, String testCasePath) {

        Long pid = problem.getId();
        Integer testCaseSource = problem.getTestCaseSource();
        List<ProblemCase> problemCase = problem.getProblemCase();

        JSONArray jsonArray = new JSONArray();

        for (int i = 0; i < problemCase.size(); i++) {

            ProblemCase aCase = problemCase.get(i);

            String name = String.valueOf(i + 1);
            String inputPath =  testCasePath + File.separator + name + ".in";
            String outputPath = testCasePath + File.separator + name + ".out";

            if(testCaseSource == null || testCaseSource == 0){
                FileUtil.writeUtf8String(aCase.getInput(), inputPath);
                FileUtil.writeUtf8String(aCase.getOutput(), outputPath);
            }else {

                String srcTestCasePath = UploadConfig.getLocalPath() + Constants.File.TESTCASE_BASE_FOLDER.getPath()
                        + File.separator
                        + pid.toString();
                FileUtil.copy(srcTestCasePath + File.separator + aCase.getInput(),
                        inputPath,true);
                FileUtil.copy(srcTestCasePath + File.separator + aCase.getOutput(),
                        outputPath,true);
            }
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("name",name);
            jsonObject.put("seq",aCase.getSeq());
            jsonObject.put("score",aCase.getScore());
            jsonArray.add(jsonObject);
        }
        //格外消息
        FileUtil.writeUtf8String(jsonArray.toJSONString(), testCasePath + File.separator + "testCaseInfo.json");

    }

    private List<String> getTags(Problem problem) {
        List<String> tags = Optional.ofNullable(problem.getTagList())
                .orElse(new ArrayList<>())
                .stream()
                .map(item -> item.getName())
                .collect(Collectors.toList());
        return tags;
    }

    private List<String> getLangNames(Problem problem) {
        return Optional.ofNullable(problem.getLanguageList())
                .orElse(new ArrayList<>())
                .stream()
                .map(item -> item.getName())
                .collect(Collectors.toList());
    }

    /**
     * 导出语言模板
     * @param problem
     * @return
     */
    private List<HashMap<String, String>> getCodeTemplates(Problem problem) {

        List<ProblemLanguage> problemLanguageList = problem.getProblemLanguageList();

        List<HashMap<String,String>> codeTemplates = new ArrayList<>();

        if(StringUtils.isEmpty(problemLanguageList))
            return codeTemplates;

        for (ProblemLanguage problemLanguage : problemLanguageList) {
            HashMap<String, String> hashMap = new HashMap<>();
            hashMap.put("code",problemLanguage.getCode());
            hashMap.put("language",problemLanguage.getName());
            codeTemplates.add(hashMap);
        }
        return codeTemplates;
    }

}




